package org.aksw.treelearning.learner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.aksw.treelearning.data.Cache;
import org.aksw.treelearning.data.CsvReader;
import org.aksw.treelearning.data.Instance;
import org.aksw.treelearning.data.Mapping;
import org.aksw.treelearning.data.SetOperations;
import org.aksw.treelearning.pseudomeasures.Measure;
import org.aksw.treelearning.pseudomeasures.PM1;
import org.aksw.treelearning.pseudomeasures.PM2;
import org.aksw.treelearning.pseudomeasures.PM3;
import org.aksw.treelearning.pseudomeasures.PM4;
import org.aksw.treelearning.tree.Condition;
import org.aksw.treelearning.tree.Node;
import org.aksw.treelearning.tree.Tree;
import org.aksw.treelearning.utils.Utils;

public class UnsupervisedLinkLearner extends ElementaryLearner {

	//private int[] defaultNgrams = new int[]{1,2,3};
	private int[] defaultNgrams = new int[]{3};
	
	private Map<String,String> propertyMap = null;
	
	private int[] ngrams = null;
	private int ngram =3; 
	
	private Measure[] measures = new Measure[]{new PM1(),new PM2(), new PM3(), new PM4()};
	private int measureIndex =1;
	private double beta=1.0f;
	
	private static double LOWBOUND = 0.3f;
	private int iteration=11;
	
	private Map<String,Mapping> mappingMap = null;
	private Mapping currentMapping = null;
	
	public UnsupervisedLinkLearner(Map m){
		this.propertyMap = m;
	}
	
	public Mapping learn(Cache source, Cache target)
    {
		
		mappingMap = new HashMap<String,Mapping>();
		// determining the best condition for each attribute, independently
		double tempPseudoFM =0;
		double maxPseudoFM =-1;
		
		Iterator<String> iter = propertyMap.keySet().iterator();
		Condition[] conditions = new Condition[propertyMap.size()];
		int condiIndex =0;
		String key = null;
		while(iter.hasNext()){
			key = iter.next();
			conditions[condiIndex]=new Condition(key,propertyMap.get(key),0.1f,ngram);
			condiIndex++;
		}
	
		System.out.println("\n===Learning threshold for each condition(attribute)===");
		
		for(int i=0;i<conditions.length;i++){
			maxPseudoFM = -1;
			for(int j=0;j<ngrams.length;j++){
				ngram = ngrams[j];
				Condition tempCondition = new Condition(conditions[i].sourceProperty,conditions[i].targetProperty,-1,ngram);
				long start = System.currentTimeMillis();
				tempCondition=getBestCondition(source, target, tempCondition, iteration);
				long end = System.currentTimeMillis();
				System.out.println("time for "+conditions[i].sourceProperty+"-"+conditions[i].targetProperty+"("+ngram+"-gram):\t"+(end-start));
				tempPseudoFM = tempCondition.fMeasure;
				if(tempPseudoFM>maxPseudoFM){
					maxPseudoFM=tempPseudoFM;
					conditions[i] = tempCondition;
				}
			}
			
			System.out.println("\n*** Best threshold for "+conditions[i].sourceProperty+"-"+conditions[i].targetProperty+"***");
			System.out.println("Threshold: >"+conditions[i].threshold+"\tQ: "+conditions[i].q+"\tPseudo-F: "+conditions[i].fMeasure);
			System.out.println("------------------------------");
			
			
			mappingMap.put(conditions[i].sourceProperty+"-"+conditions[i].targetProperty, currentMapping);
			
		}
		
//		for(int i=0;i<comparableProperties.length;i++){
//			maxPseudoFM = -1;
//			for(int j=0;j<ngrams.length;j++){
//				ngram = ngrams[j];
//				Condition tempCondition = new Condition(comparableProperties[i],-1,ngram);
//				long start = System.currentTimeMillis();
//				tempCondition=getBestCondition(source, target, tempCondition, iteration);
//				long end = System.currentTimeMillis();
//				System.out.println("time to get threshold for "+comparableProperties[i]+"("+ngram+"-gram):\t"+(end-start));
//				tempPseudoFM = tempCondition.fMeasure;
//				if(tempPseudoFM>maxPseudoFM){
//					maxPseudoFM=tempPseudoFM;
//					conditions[i] = tempCondition;
//				}
//			}
//			
//			mappingMap.put(conditions[i].sourceProperty+"-"+conditions[i].targetProperty, currentMapping);
//		}

		
		Arrays.sort(conditions, new ComparatorForCondition());
		
		
		
		for(Condition c:conditions){
			System.out.println(c.toString());
		}
		
		System.out.println("\n===Learning logical combination of conditions for leanring tree structure===");
		
		
		String cacheMappingKey = conditions[0].sourceProperty+"-"+conditions[0].targetProperty;
		
		Mapping map1 = mappingMap.get(cacheMappingKey);

		ArrayList<Condition> finalConditionList = new ArrayList<Condition>();
		
		int bestMappingCase =0;
		int bestMappingIndex=0;
		String[] attachTypes = new String[]{"Intersection","Front-Back","Back-Front","Union"};
		ArrayList<String> attachTypeList = new ArrayList<String>();
		String currentMappingLogic = "("+cacheMappingKey+">"+conditions[0].threshold+")";
		String candidateMappingLgoic = null;
		
		finalConditionList.add(conditions[0]);
		
		boolean finished = false;
		boolean[] usedConditions = new boolean[conditions.length];
		Arrays.fill(usedConditions, false);
		usedConditions[0]=true;
		maxPseudoFM =conditions[0].fMeasure;
		
		while(!finished){
			bestMappingCase = -1;
			bestMappingIndex = -1;
			
			for(int i=1;i<usedConditions.length;i++){
				
				if(!usedConditions[i]){
					cacheMappingKey =conditions[i].sourceProperty+"-"+conditions[i].targetProperty;
					Mapping map2 = mappingMap.get(cacheMappingKey);
					
					Mapping[] mappingCases = new Mapping[]{
							SetOperations.intersection(map1, map2),
							SetOperations.difference(map1, map2),
							SetOperations.difference(map2, map1),
							SetOperations.union(map1, map2)
							
					};
						
					int j=0;
					
					
					for(;j<mappingCases.length;j++){
						tempPseudoFM= measures[measureIndex].getPseudoFMeasure(source.getAllUris(), target.getAllUris(),mappingCases[j],beta);
						candidateMappingLgoic = currentMappingLogic+" "+attachTypes[j]+" "+"("+cacheMappingKey+">"+conditions[i].threshold+")";
						System.out.println(candidateMappingLgoic+":\t"+tempPseudoFM);
						if(tempPseudoFM>maxPseudoFM){
							maxPseudoFM=tempPseudoFM;
							bestMappingIndex = i;
							bestMappingCase = j;
						}
					}
					
					
						
				}
				
				
			}
			
			if(bestMappingIndex>0){
				cacheMappingKey=conditions[bestMappingIndex].sourceProperty+"-"+conditions[bestMappingIndex].targetProperty;
				currentMappingLogic = currentMappingLogic+"\t"+attachTypes[bestMappingCase]+" "+cacheMappingKey;
				attachTypeList.add(attachTypes[bestMappingCase]);
				finalConditionList.add(conditions[bestMappingIndex]);
				
				usedConditions[bestMappingIndex]=true;
			}
			else{
				break;
			}
			
			boolean temp = true;
			for(boolean f:usedConditions){
				temp=temp&&f;
			}
			finished = temp;
			
		}	
			
		this.printLearningLogic(finalConditionList, attachTypeList);
		
		return map1;
    }
	
	
	
	/*
	 * 
	 */
	public Condition getBestCondition(Cache source, Cache target, String sourceProperty, String targetProperty, int iterations)
    {
		
		System.out.println("\n===Learning a threshold for "+sourceProperty+"-"+targetProperty+":iteration-"+iterations+", Q-"+ngram+"===");
		
		double threshold = this.LOWBOUND;
		
		double tempPseudoFM =0;
		Mapping mapping = Utils.computeMapping(source, target, sourceProperty, targetProperty, threshold, ngram);
		double maxPseudoFM =measures[measureIndex].getPseudoFMeasure(source.getAllUris(), target.getAllUris(), mapping,beta);
		double tempThreshold =0;
		Mapping subMapping = null;
		double gap = (1-this.LOWBOUND)/iteration;
		currentMapping = mapping;
		for(int i=0;i<iteration;i++){
			tempThreshold = this.LOWBOUND+gap*i;
			subMapping = mapping.getSubMap(threshold+gap*i);
			tempPseudoFM=measures[measureIndex].getPseudoFMeasure(source.getAllUris(), target.getAllUris(), subMapping,beta);
			System.out.println("Threshold: >"+tempThreshold+"\tPseudo-F: "+tempPseudoFM);
			if(tempPseudoFM>maxPseudoFM){
				maxPseudoFM=tempPseudoFM;
				threshold = tempThreshold;
				currentMapping = subMapping;
			}
			
		}
		Condition condition = new Condition(sourceProperty,targetProperty,threshold,ngram);
		condition.fMeasure=maxPseudoFM;
		return condition;
    }
	
	
	
	/*
	 * 
	 */
	public void init(int measureIndex,int[] ngrams, int iteration,double beta){
		
		if(measureIndex>=0)
			this.measureIndex=measureIndex;
		if(iteration>0)
			this.iteration=iteration;
		
		this.beta=beta;
		
		if(ngrams==null){
			this.ngrams=this.defaultNgrams;
		}
		else{
			this.ngrams=ngrams;
		}
		

	}
	
	
	
	private class ComparatorForCondition implements Comparator{

		@Override
		public int compare(Object arg0, Object arg1) {
			// TODO Auto-generated method stub
			Condition firstOne = (Condition) arg0;
			Condition secondOne = (Condition) arg1;
			
			if(firstOne.fMeasure<secondOne.fMeasure)
				return 1;
			else if(firstOne.fMeasure>secondOne.fMeasure)
				return -1;
			
			return 0;
		}
		
	}
	
	private void printLearningLogic(ArrayList<Condition> conditions, ArrayList<String> attachTypeList){
		StringBuffer sb = new StringBuffer();
		sb.append("\n===Final learning condition===\n");
		sb.append("("+conditions.get(0).sourceProperty+"-"+conditions.get(0).targetProperty+">"+conditions.get(0).threshold+")");
		for(int i=0;i<attachTypeList.size();i++){
			sb.append(" "+attachTypeList.get(i)+" ");
			sb.append(conditions.get(i+1).sourceProperty+"-"+conditions.get(i+1).targetProperty+">"+conditions.get(i+1).threshold);
				
		}
		System.out.println(sb.toString());
	}
	
	private Condition[] getMeaningfulAttributeAlign(Cache sourceSample, Cache targetSample){
		Instance instSource = sourceSample.getAllInstances().get(0);
		ArrayList<String> sourcePropSet =new ArrayList<String>(instSource.getAllProperties());
		Instance instTarget = sourceSample.getAllInstances().get(0);
		ArrayList<String> targetPropSet =new ArrayList<String>(instSource.getAllProperties());
		int maxNumOfCondition = Math.min(sourcePropSet.size()-1, targetPropSet.size()-1);
		ArrayList<Condition> conList = new ArrayList<Condition>();
		for(int i=1;i<sourcePropSet.size();i++){
			for(int j=1;j<targetPropSet.size();j++){
				Mapping mapping = Utils.computeMapping(sourceSample, targetSample, sourcePropSet.get(i), targetPropSet.get(j), 0.3, ngram);
				Condition c = new Condition(sourcePropSet.get(i),targetPropSet.get(j),0.3,ngram);
				c.fMeasure=mapping.size();
				conList.add(c);
			}
		}
		
		Condition[] conditions = new Condition[conList.size()];
		conList.toArray(conditions);
		
		Arrays.sort(conditions, new ComparatorForCondition());
		
		ArrayList<Condition> finalCondition = new ArrayList<Condition>();
		ArrayList<String> usedSourcePropList = new ArrayList<String>();
		ArrayList<String> usedTargetPropList = new ArrayList<String>();
		int count =0;
		for(Condition condi:conditions){
			if(!usedSourcePropList.contains(condi.sourceProperty)&&
					!usedTargetPropList.contains(condi.targetProperty)){
				count++;
				finalCondition.add(condi);
				usedSourcePropList.add(condi.sourceProperty);
				usedTargetPropList.add(condi.targetProperty);
				if(count==maxNumOfCondition)
					break;
			}
		}
		
		Condition[] finalOnes = new Condition[finalCondition.size()];
		finalCondition.toArray(finalOnes);
		
		return finalOnes;
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String sourceCSV = System.getProperty("user.dir")+"/resources/DBLP-ACM/ACM.csv";
		String targetCSV = System.getProperty("user.dir")+"/resources/DBLP-ACM/DBLP.csv";
		//String sourceCSV = System.getProperty("user.dir")+"/resources/DBLP-Scholar/Scholar.csv";
		//String targetCSV = System.getProperty("user.dir")+"/resources/DBLP-Scholar/DBLP.csv";
		Map<String,String> propertyMap = new HashMap<String,String>();
		propertyMap.put("title", "title");
		propertyMap.put("authors", "authors");
		propertyMap.put("venue", "venue");
		propertyMap.put("year", "year");
		UnsupervisedLinkLearner learner = new UnsupervisedLinkLearner(propertyMap);
		
		learner.init(1,new int[]{3},10,1.0);
		
		Cache source = CsvReader.readFile(sourceCSV);
		Cache target = CsvReader.readFile(targetCSV);
		System.out.println("read source:\t"+source.size());
		System.out.println("read target:\t"+target.size());
		learner.learn(source, target);
	}

}
